package com.keshi.openplatform.gateway.filters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.keshi.openplatform.commons.beans.BaseResultBean;
import com.keshi.openplatform.commons.constans.ExceptionDict;
import com.keshi.openplatform.commons.constans.SystemParams;
import com.keshi.openplatform.gateway.feign.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 这个是针对所有需要幂等性校验的服务进行的过滤器
 */
@Component
public class IdempotentsFilter extends ZuulFilter {
    @Autowired
    private CacheService cacheService;
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 40;
    }

    /**
     * 如果前面的过滤器拦截了请求,则不执行,同时如果当前请求的服务是一个费幂等性的服务,也不需要进行拦截,所以此处需要两个判断条件
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        //根据用户请求的服务,获取到服务的信息,然后看看服务是不是需要幂等性的要求
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String method = request.getParameter("method");//用户要请求的服务的名字
        //通过查询redis来获取这个服务的幂等性
        boolean isIdempotents = true;//幂等性默认是true
        try {
            String idempotents = cacheService.hGet(SystemParams.METHOD_REDIS_PRE + method,"idempotents");
            isIdempotents = "1".equals(idempotents);//判断是不是幂等性的
        } catch (Exception e) {
            e.printStackTrace();
        }

        return context.sendZuulResponse() && isIdempotents;//启用的条件中包含了是不是幂等性要求的服务
    }

    @Override
    public Object run() throws ZuulException {
        //我们当前的过滤器是要判断请求是不是已经执行过一次了
        //根据我们的要求我们需要什么数据来进行这个判断
        //当前的请求是什么?我们如何区分的
        //我们如何知道请求已经执行过一次了,大家注意,我们遇到这个如何判断,如何区分,怎么样xxx之类的需求的时候一般都是需要数据去做比较,那么数据一定是保存在某个地方,比如某个变量,放在数据库,放在缓存,或者是在什么容器中等等
        //我们想一下,要想判断请求是不是执行过,需要请求有一个唯一的可以区分的标识,签名,签名是唯一的,因为不同的参数会有不同的签名,不同的app会有不同的appkey,相同的app不同的服务会有不同的method,相同的服务会有不同的参数,相同的参数中会有不同的时间戳
        //如果一切都一样,说明就是一个一样的请求
        //请求被执行过一次之后我们可以将标识保存起来,当下次请求再来的时候我们拿到签名去看看这个标识存在还是不存在,不存在则代表没有执行过,存在则代表执行过
        //实际上我们可以签名作为key向redis中保存任意一个数据,我们首先获取,没有就代表没有,有就代表执行过
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String sign = request.getParameter("sign");//获取用户传递的签名
        try {
            String idempotents = cacheService.getFromRedis(SystemParams.IDEMPOTENTS_REDIS_PRE + sign);//获取当前签名对应的数据
            //如果不为空则代表是已经请求过了,如果为空则代表没有请求过
            if (idempotents != null) {
                context.setSendZuulResponse(false);
                HttpServletResponse response = context.getResponse();
                response.setContentType("appliaction/json;charset=utf-8");
                BaseResultBean bean = new BaseResultBean();
                bean.setCode(ExceptionDict.SYSTEMPARAM_IDEMPOTENTS_ERROR);
                bean.setMsg("当前服务不允许重复提交数据");
                try {
                    context.setResponseBody(objectMapper.writeValueAsString(bean));
                } catch (JsonProcessingException ex) {
                    ex.printStackTrace();
                }
            }else{
                //有效期不会是永久的,因为一旦时间戳过期了,用户一定要传递新的时间,那么签名一定会发生变化,所以我们的有效期和时间戳的有效期保持一致即可
                cacheService.save2Redis(SystemParams.IDEMPOTENTS_REDIS_PRE + sign, System.currentTimeMillis()+"",60000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}